from threading import Thread
from typing import Optional

import uvicorn
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

from pyminer2.workspace.datamanager.datamanager import DataManager
from pyminer2.workspace.datamanager.exceptions import ConflictError, NotFoundError
from pyminer2.workspace.datamanager.converter import ConverterError
from pyminer2.workspace.datamanager.metadataset import WouldBlockError
from pyminer2.workspace.datamanager.variable import VariableError

class Data(BaseModel):
    dataname: str
    data: dict
    provider: Optional[str] = 'server'

class DataServer(Thread):
    def __init__(self, datamanager:DataManager, async_work, *args, **kwargs):
        super().__init__()
        self.datamanager = datamanager
        self.async_work = async_work
        self.args = args
        self.kwargs = kwargs
        self.define_errors()
        self.app = FastAPI()

        @self.app.get('/read/{dataname}')
        def read_data(dataname:str):
            try:
                return self.datamanager.read_data(dataname)
            except WouldBlockError as e:
                raise self.error(e, self.WOULD_BLOCK_ERROR)
            except NotFoundError as e:
                raise self.error(e, self.NOT_FOUND_ERROR)
            except ConverterError as e:
                # This mean unsupported type obj is requested
                # Users should regard this as not found error
                raise self.error(e, self.NOT_FOUND_ERROR)
            except Exception as e:
                raise self.error(e, self.INTERNAL_ERROR)

        @self.app.post('/write')
        def write_data(data:Data):
            try:
                self.datamanager.write_data(data.dataname, data.data, data.provider)
                return {'message': 'success'}
            except WouldBlockError as e:
                raise self.error(e, self.WOULD_BLOCK_ERROR)
            except AssertionError as e:
                raise self.error(e, self.INVALID_VALUE_ERROR)
            except ConflictError as e:
                raise self.error(e, self.CONFLICT_ERROR)
            except Exception as e:
                raise self.error(e, self.INTERNAL_ERROR)

    def start(self):
        super().start()
        uvicorn.run(self.app, host='127.0.0.1', port=8783)

    def run(self):
        self.async_work(*self.args, **self.kwargs)

    def define_errors(self):
        self.WOULD_BLOCK_ERROR = 'WOULD_BLOCK_ERROR'
        self.NOT_FOUND_ERROR = 'NOT_FOUND_ERROR'
        self.INTERNAL_ERROR = 'INTERNAL_ERROR'
        self.INVALID_VALUE_ERROR = 'INVALID_VALUE_ERROR'
        self.CONFLICT_ERROR = 'CONFLICT_ERROR'

    def error(self, e, code):
        return HTTPException(status_code=404, detail=code, headers={'msg':str(e)})
